﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace SimpleSocket
{

    /// <summary>
    /// Used to read the data from the Socket and combine it into a single message.
    /// </summary>
    internal class MessageReceiver : IDisposable
    {
        /// <summary>
        /// Use this buffer for the socket ReceiveCalls
        /// </summary>
        public byte[] ReceiveBuffer;

        DateTime ReceiveStart;
        MessageFlags Flags;
        MessagePartReceiver flagsReceiver = new MessagePartReceiver(4);


        MessagePartReceiver responceIDReceiver = new MessagePartReceiver(4);
        MessagePartReceiver intHeaderReceiver = new MessagePartReceiver(4);

        MessagePartReceiver headerStrLenReceiver = new MessagePartReceiver(4);
        MessagePartReceiver headerStrReceiver = new MessagePartReceiver(0);


        MessagePartReceiver lengthReceiver = new MessagePartReceiver(4);
        MessagePartReceiver messageReceiver = new MessagePartReceiver(0);

        int Position = 0;
        int BytesReceived = 0;

        SimpleSocketClient Client;

        public MessageReceiver(SimpleSocketClient client)
        {
            Client = client;
            ReceiveBuffer = new byte[1024];
        }

        /// <summary>
        /// Process the Buffer reading the data upto the byteReceived.
        /// </summary>
        /// <param name="bytesReceived"></param>
        public void DataReceived(int bytesReceived)
        {
            Position = 0;
            BytesReceived = bytesReceived;

            // for loop just to make sure it does not loop more
            // then the size of the buffer for extra security.
            for (int x = 0; x <= ReceiveBuffer.Length; x++ )
            {
                // Message flags
                if (!flagsReceiver.Complete)
                {
                    flagsReceiver.ReadData(BytesReceived, ReceiveBuffer, ref Position);
                    if (flagsReceiver.Complete)
                    {
                        Flags = (MessageFlags)BitConverter.ToInt32(flagsReceiver.Data, 0);
                        ReceiveStart = DateTime.Now;
                    }
                    else
                    {
                        return;
                    }
                }

                // ResponceID
                if (ReceiveMessage(responceIDReceiver, MessageFlags.Responce, MessageFlags.ResponceRequest) == false) return;

                // Int Header
                if (ReceiveMessage(intHeaderReceiver, MessageFlags.Command) == false) return;

                // String Header
                if (ReceiveLength(headerStrLenReceiver, headerStrReceiver, MessageFlags.CommandText) == false) return;
                if (ReceiveMessage(headerStrReceiver, MessageFlags.CommandText) == false) return;

                // Message
                if (ReceiveLength(lengthReceiver, messageReceiver, MessageFlags.DataPackage) == false) return;
                if (ReceiveMessage(messageReceiver, MessageFlags.DataPackage) == false) return;


                // if we made it here we have received a complete message
                Message message = new Message();

                message.ReceiveStart = ReceiveStart;
                message.ReceiveComplete = DateTime.Now;

                if (Helpers.HasFlag(Flags, MessageFlags.ResponceRequest))
                    message.ResponceRequested = true;

                if (Helpers.HasFlag(Flags, MessageFlags.Responce) || Helpers.HasFlag(Flags, MessageFlags.ResponceRequest))
                    message.ResponceID = BitConverter.ToUInt32(responceIDReceiver.Data, 0);

                if (Helpers.HasFlag(Flags, MessageFlags.Command))
                    message.Command = BitConverter.ToInt32(intHeaderReceiver.Data, 0);

                if (Helpers.HasFlag(Flags, MessageFlags.DataPackage))
                    message.ReceivedData = messageReceiver.Data;

                if (Helpers.HasFlag(Flags, MessageFlags.CommandText))
                    message.CommandText = Encoding.UTF8.GetString(headerStrReceiver.Data);

                    // TODO: Pass the message off
                Client.MessageComplete(Flags, message);

                // Process of message complete reset the receivers
                Reset();

                if (Position == BytesReceived)
                    return;
            }
        }


        private bool ReceiveMessage(MessagePartReceiver msgReceiver, params MessageFlags[] flags)
        {
            if (Helpers.HasFlag(Flags, flags) && !msgReceiver.Complete)
            {
                msgReceiver.ReadData(BytesReceived, ReceiveBuffer, ref Position);
                if (!msgReceiver.Complete)
                    return false;
            }

            return true;
        }

        private bool ReceiveLength(MessagePartReceiver lenReceiver, MessagePartReceiver dataReceiver,  params MessageFlags[] flags)
        {
            if (Helpers.HasFlag(Flags, flags) && !lenReceiver.Complete)
            {
                lenReceiver.ReadData(BytesReceived, ReceiveBuffer, ref Position);
                if (lenReceiver.Complete)
                {
                    int length = BitConverter.ToInt32(lenReceiver.Data, 0);
                    dataReceiver.Length = length;
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Restart the message receiver to start receiving from the begining.
        /// </summary>
        public void Reset()
        {
            flagsReceiver.Reset();
            responceIDReceiver.Reset();
            intHeaderReceiver.Reset();

            headerStrLenReceiver.Reset();
            headerStrReceiver.Length = 0;

            lengthReceiver.Reset();
            messageReceiver.Length = 0;
        }


        public void Dispose()
        {
            flagsReceiver = null;
            responceIDReceiver = null;
            intHeaderReceiver = null;
            headerStrLenReceiver = null;
            headerStrReceiver = null;
            lengthReceiver = null;
            messageReceiver = null;
            Client = null;
        }
    }
}
